﻿#define _DEBUG_MODE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace PHPDecoder
{
    public partial class myDecoder
    {
        #region PROPERTIES
        /// <summary>
        /// Vysledok analyzy
        /// </summary>
        public AnalyseResult result;
        // Operatory v php
        private char[] operators = new char[] { '+', '-', '*', '/', '%', '&', '^', '|', '.', '~', '>', '<', '=' };
        private char[] changeoperators = new char[] { '+', '-', '*', '/', '.' };
        private char[] whitespace = new char[] { '\n', '\r', ' ', '\t' };

        string debug_method = "PMA_convert_file";

        private RotBuffer buff = new RotBuffer(50);
        /// <summary>
        /// Suborovy stream na dekodovanie
        /// </summary>
        StreamReader reader = null;
        private int currChar = 0;
        private PhpAutomat aut = null;
        private bool isInPhp = false;
        private delegate void KeywordHandlerFunction();
        private Dictionary<PHPKEYWORDS, KeywordHandlerFunction> callbacks;
        private char endChar = '"';
        private ClassNode currClsNode = null;
        private FunctionNode currFuncNode = null;
        private FunctionNode CallFunctionNode = null;
        private VariableNode currVarNode = null;
        private VariableNode currDecNode = null;
        bool firstPass = false;
        // Dekodovanie hodnoty resp. pravej casti vyrazu
        bool decVal = false;
        bool varchange = false;
        private StringBuilder name = null;
        string path = null;
        string workingDirectory = null;
        string op = null;
        Stack<StreamReader> readers = new Stack<StreamReader>();
        // $x->y
        private bool clsval = false;
        #endregion

        #region Constructors
        public myDecoder(String path)
        {
            this.path = path;
            this.reader = new StreamReader(path);
            Init();
            workingDirectory = Path.GetDirectoryName(path);
        }

        

        #endregion

        #region INIT
        
        private void Init()
        {
            aut = new PhpAutomat();
            result = new AnalyseResult();
            callbacks = new Dictionary<PHPKEYWORDS, KeywordHandlerFunction>();
            BuildCallbackDictionary();
            currClsNode = result.getClass(null);
            currFuncNode = null;
        }

        private void BuildCallbackDictionary()
        {
            callbacks.Add(PHPKEYWORDS.PUBLICMODIFIER, new KeywordHandlerFunction(ModifierDecode));
            callbacks.Add(PHPKEYWORDS.PRIVATEMODIFIER, new KeywordHandlerFunction(ModifierDecode));
            callbacks.Add(PHPKEYWORDS.STATICMODIFIER, new KeywordHandlerFunction(ModifierDecode));
            callbacks.Add(PHPKEYWORDS.PROTECTEDMODIFIER, new KeywordHandlerFunction(ModifierDecode));
            callbacks.Add(PHPKEYWORDS.ABSTRACTMODIFIER, new KeywordHandlerFunction(ModifierDecode));
            callbacks.Add(PHPKEYWORDS.CLASS, new KeywordHandlerFunction(ClassDecode));
            callbacks.Add(PHPKEYWORDS.INTERFACE, new KeywordHandlerFunction(InterfaceDecode));
            callbacks.Add(PHPKEYWORDS.METHOD, new KeywordHandlerFunction(MethodDecode));
            callbacks.Add(PHPKEYWORDS.VARIABLE, new KeywordHandlerFunction(VariableDecode));
            callbacks.Add(PHPKEYWORDS.GLOBAL, new KeywordHandlerFunction(GlobalDecode));
            callbacks.Add(PHPKEYWORDS.TRY, new KeywordHandlerFunction(TryDecode));
            callbacks.Add(PHPKEYWORDS.THROW, new KeywordHandlerFunction(ThrowDecode));
            callbacks.Add(PHPKEYWORDS.IF, new KeywordHandlerFunction(IfDecode));
            callbacks.Add(PHPKEYWORDS.WHILE, new KeywordHandlerFunction(WhileDecode));
            callbacks.Add(PHPKEYWORDS.DO, new KeywordHandlerFunction(DoDecode));
            callbacks.Add(PHPKEYWORDS.FOR, new KeywordHandlerFunction(ForDecode));
            callbacks.Add(PHPKEYWORDS.FOREACH, new KeywordHandlerFunction(ForeachDecode));
            callbacks.Add(PHPKEYWORDS.SWITCH, new KeywordHandlerFunction(SwitchDecode));
            callbacks.Add(PHPKEYWORDS.CASE, new KeywordHandlerFunction(CaseDecode));
            callbacks.Add(PHPKEYWORDS.OTHERS, new KeywordHandlerFunction(OtherDecode));
            callbacks.Add(PHPKEYWORDS.RETURN, new KeywordHandlerFunction(ReturnDecode));
            callbacks.Add(PHPKEYWORDS.ECHO, new KeywordHandlerFunction(EchoDecode));
            callbacks.Add(PHPKEYWORDS.PHPSTART, new KeywordHandlerFunction(PHPStartDecode));
            callbacks.Add(PHPKEYWORDS.PHPEND, new KeywordHandlerFunction(PHPEndDecode));
            callbacks.Add(PHPKEYWORDS.NOP, new KeywordHandlerFunction(NOPDecode));
            callbacks.Add(PHPKEYWORDS.COMMENTS_1, new KeywordHandlerFunction(Comment1Decode));
            callbacks.Add(PHPKEYWORDS.COMMENTS_2, new KeywordHandlerFunction(Comment2Decode));
            callbacks.Add(PHPKEYWORDS.INCLUDE, new KeywordHandlerFunction(IncludeDecode));
            callbacks.Add(PHPKEYWORDS.INCLUDE_ONCE, new KeywordHandlerFunction(IncludeDecode));
            callbacks.Add(PHPKEYWORDS.REQUIRE, new KeywordHandlerFunction(RequireDecode));
            callbacks.Add(PHPKEYWORDS.REQUIRE_ONCE, new KeywordHandlerFunction(RequireDecode));
            callbacks.Add(PHPKEYWORDS.BREAK, new KeywordHandlerFunction(BreakDecode));
            callbacks.Add(PHPKEYWORDS.VAR, new KeywordHandlerFunction(NOPDecode));
            callbacks.Add(PHPKEYWORDS.ELSE, new KeywordHandlerFunction(ElseDecode));
            callbacks.Add(PHPKEYWORDS.ELSEIF, new KeywordHandlerFunction(IfDecode));
        }
        
        #endregion

        #region AUXILIARY_FUNCTIONS

        /// <summary>
        /// Nacitanie dalsieho znaku (F)
        /// </summary>
        private void ReadNextChar()
        {
            if (buff.ToString().IndexOf("inside quotation marks") != -1)
            {
            }
            int last = currChar;
            currChar = reader.Read();
            buff.Add((char)currChar);
            if (currChar == '/' && last == '/') 
                Comment1Decode();
            if (currChar == '*' && last == '/') 
                Comment2Decode();
        }

        /// <summary>
        /// Zisti ci je ukoncovaci znak pre nazov funkcie, tzn. biele znaky alebo lava zatvorka (F)
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private bool isBreakChar(char c)
        {
            return (isWhiteSpace(c) || c == '(' || c == ')' || c == ';' || c == ':');
        }

        /// <summary>
        /// Zisti ci je biely znak
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private bool isWhiteSpace(char c)
        {
            foreach (char cc in whitespace)
            {
                if (cc == c) return true;
            }
            return false;
        }
        /// <summary>
        /// Citanie znaku az kym nenarazi na nejaky neprazdny znak (F)
        /// </summary>
        private void SkipWS()
        {
            while (isWhiteSpace((char)currChar)) ReadNextChar();
        }
        /// <summary>
        /// Nacitanie cesty // pre include("cesta");
        /// </summary>
        /// <returns></returns>
        private string ReadPath()
        {
            bool b = false;
            int cc = ';';
            if (currChar == '(')
            {
                b = true;
                ReadNextChar();
                cc = ')';
            }
            StringBuilder sb = new StringBuilder();
            SkipWS();

            if (currChar == '\'' || currChar == '"')
            {
                cc = currChar;
            }
            ReadNextChar();
            while (currChar != cc)
            {
                sb.Append((char)currChar);
                ReadNextChar();
            }
            if (currChar == '"' || currChar == '\'') ReadNextChar();
            SkipWS();
            if (b) ReadNextChar();
            SkipWS();
            ReadNextChar(); //';'
            return sb.ToString();
        }

        /// <summary>
        /// aplikacia relativnej cesty na absolutnu cestu, napr. "C:\Users\test.abc" a "..\test.def" => "C:\test.def"
        /// </summary>
        String Sum(string absolutePath, string relativePath)
        {
            Regex r = new Regex(@"[A-Za-z]\:.*");
            if (r.IsMatch(relativePath)) return relativePath;

            string[] str = relativePath.Split(new char[] { '\\', '/' });
            string[] str2 = absolutePath.Split(new char[]{'\\','/'});

            int index = 0;
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i]!="." && str[i]!=".." && str[i]!="") break;
                if (str[i] == "..") index++;
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < str2.Length - index; i++)
            {
                sb.Append(str2[i] + "\\");
            }
            for (int i = index; i < str.Length - 1; i++)
            {
                sb.Append(str[i] + "\\");
            }
            sb.Append(str[str.Length - 1]);
            return sb.ToString();
        }

        private string standardize(String filename)
        {
            StringBuilder sb = new StringBuilder();
            char last = ' ';
            for (int i = 0; i < filename.Length; i++)
            {
                if (filename[i] != '.')
                {
                    if ((filename[i] != '/' || last != '/') && (filename[i] != '\\' || last != '\\'))
                    {
                        sb.Append(filename[i]);
                        last = filename[i];
                    }
                }
                else
                {
                    if ((last == '/' && filename.Length > i + 1 && filename[i + 1] == '/') || (last == '\\' && filename.Length > i + 1 && filename[i + 1] == '\\'))
                    {
                    }
                    else
                    {
                        last = filename[i];
                        sb.Append(filename[i]);
                    }
                }
            }
            return sb.ToString();
        }

        private bool isDecoded(String filename)
        {
            string f = standardize(filename);
            foreach (string fi in result.decodedFile.Keys)
            {
                if (fi == f)
                {
                    return true;
                }
            }
            return false;
        }

        private VariableNode getVariable(string varName)
        {
            VariableNode node = null;
            if (currFuncNode != null)
            {
                if (isGlobalVariable(currFuncNode, ref varName))
                {
                    node = getGlobalClass().getVariable(varName);
                    if (currFuncNode != null && !currFuncNode.globalVars.ContainsKey(varName))
                    {
                        currFuncNode.globalVars.Add(varName, node);
                    }
                }
                else
                {
                    if (isFormalParameter(currFuncNode, varName))
                    {
                        node = currFuncNode.getFormalParameter(varName);
                        if (node == null) node = new VariableNode(varName);
                    }
                    else
                    {
                        node = new VariableNode(varName);
                        if (!currFuncNode.localVars.ContainsKey(varName))
                            currFuncNode.localVars.Add(varName, node);
                    }
                }
            }
            else
            {
                if (currClsNode != getGlobalClass())
                {
                    node = currClsNode.getVariable(varName);
                }
                else
                {
                    node = getGlobalClass().getVariable(varName);
                }
            }
            return node;
        }

        /// <summary>
        /// Metoda hlada klucove slova v php skripte (F)
        /// </summary>
        /// <returns>dalsie klucove slovo v php skripte</returns>
        public PHPKEYWORDS getNextKeyword()
        {
            SkipWS();
            if (currChar == '('|| currChar == ':') ReadNextChar();
            Node n = aut.tree.root;
            if (n == null || currChar == -1) return PHPKEYWORDS.NOP;
            StringBuilder sb = new StringBuilder();
            while (true)
            {
                //if (isBreakChar((char)currChar)) return PHPKEYWORDS.OTHERS;

                if (currChar == -1)
                {
                    if (n.bResult) return n.result;
                    return PHPKEYWORDS.NOP;
                }

                if (currChar == '(' || currChar == ':')
                {
                    if (n.bResult) return n.result;
                    name = sb;
                    return PHPKEYWORDS.OTHERS;
                }

                if (currChar == '\\')
                {
                    ReadNextChar();
                    continue;
                }

                n = aut.tree.Krok(n, (char)currChar);
                sb.Append((char)currChar);
                ReadNextChar();
                //Ak je vysledny vrchol a existuje dopredna hrana, tak pokracuje, inak vrati vysledok
                //Tym stratime napr "for" v retazci "fore[?]-{a}", ale to nam nevadi, pretoze to nie je ziadne klucove slovo
                if (n.bResult)
                {
                    if (n.result == PHPKEYWORDS.PHPSTART || 
                        n.result == PHPKEYWORDS.VARIABLE ||
                        n.result == PHPKEYWORDS.COMMENTS_1 ||
                        n.result == PHPKEYWORDS.COMMENTS_2) 
                        return n.result;
                    
                    if (n.aMap.ContainsKey((char)currChar))
                    {
                        n = aut.tree.Krok(n, (char)currChar);
                        ReadNextChar();
                    }
                    else
                    {
                        if (isBreakChar((char)currChar)) break;
                        else
                        {
                            n = aut.tree.Krok(n, (char)currChar);
                            ReadNextChar();
                        }
                    }
                }
            }
            if (n.bResult)
                return n.result;
            else
                return PHPKEYWORDS.OTHERS;
        }

        /// <summary>
        /// Zisti dalsie slovo az po biele znaky  (F)
        /// </summary>
        /// <returns>najdene slovo</returns>
        public String GetNextString()
        {
            StringBuilder sb = new StringBuilder();
            while (true)
            {
                if (currChar == -1) return null;
                if (isBreakChar((char)currChar)) break;
                else
                {
                    sb.Append((char)currChar);
                    ReadNextChar();
                }
            }
            return sb.ToString();
        }

        private String ReadNextOperator()
        {
            SkipWS();
            StringBuilder sb = new StringBuilder();
            while (isOperator((char)currChar))
            {
                sb.Append((char)currChar);
                ReadNextChar();
            }
            return sb.ToString();
        }
        /// <summary>
        /// Citat az do ';'
        /// </summary>
        /// <returns></returns>
        public String ReadUntilSemicolons()
        {
            ReadNextChar();
            StringBuilder sb = new StringBuilder();
            while (true)
            {
                if (currChar == -1) return null;

                if (currChar == '\\')
                {
                    sb.Append((char)currChar);
                    ReadNextChar();
                    sb.Append((char)currChar);
                }

                if (currChar == ';') break;

                sb.Append((char)currChar);
                ReadNextChar();
            }

            return sb.ToString();
        }

        /// <summary>
        /// Vrati funkciu na dekodovanie aktualneho klucoveho slova (F)
        /// </summary>
        /// <param name="k">Klucove slovo</param>
        /// <returns>delegat na funkciu</returns>
        private KeywordHandlerFunction GetHandleFunction(PHPKEYWORDS k)
        {
            return callbacks[k];
        }

        #endregion
    }
}
